import numpy as np


class Graph:
    """ 计算图 """
    def __init__(self):
        self.operations = []
        self.placeholders = []
        self.variables = []

    def as_defaults(self):
        """ 默认计算出图 """
        global _default_graph  # 获取全局变量
        _default_graph = self


class Operation:
    def __init__(self, input_nodes=[]):
        self.input_nodes = input_nodes  # 输入该操作的节点
        self.consumers = []  # 消费者列表 = 使用该操作的变量
        for input_node in input_nodes:
            input_node.consumers.append(self)
        _default_graph.operations.append(self)  # 加入默认计算图

    def __call__(self, *args, **kwargs):
        pass


class Placeholder:
    """ 占位符 """
    def __init__(self):
        self.consumers = []
        _default_graph.placeholders.append(self)


class Variable:
    def __init__(self, initial_value=None):
        self.value = initial_value
        self.consumers = []
        _default_graph.variables.append(self)


class Matmul(Operation):
    def __init__(self, x, y):
        super().__init__([x, y])

    def compute(self, x_value, y_value):
        return x_value.dot(y_value)


class Add(Operation):
    def __init__(self, x, y):
        super().__init__([x, y])

    def compute(self, x_value, y_value):
        return x_value + y_value


# 遍历计算图中的节点
def traverse_placeholder(operation):
    nodes_postorder = []

    def recurse(node):
        if isinstance(node, Operation):
            for input_node in node.input_nodes:
                recurse(input_node)
        nodes_postorder.append(node)

    recurse(operation)
    return nodes_postorder


class Session:
    def run(self, operation, feed_dict={}):
        """
        计算操作的输出，只需要提供相应的 placeholder 即可
        :param operation: 计算其输出的操作
        :param feed_dict: placeholder 提供的数据
        :return: 返回最顶层操作的数值
        """
        nodes_postorder = traverse_placeholder(operation)
        for node in nodes_postorder:
            if isinstance(node, Placeholder):
                node.output = feed_dict[node]  # 获得用户输入值并输出
            elif isinstance(node, Variable):
                node.output = node.value  # 变量值本身就是输出
            else:
                node.inputs = [input_node.output for input_node in node.input_nodes]  # 输入操作的节点
                node.output = node.compute(*node.inputs)
            if isinstance(node.output, list):
                node.output = np.array(node.output)

        return operation.output


